﻿
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using WindowsAPI;

namespace System.IO
{
    public static class FileSystemInfoEx
    {
        //public static Process GetLockedProcess(this FileSystemInfo Info)
        //{


        //    return null;
        //}

        public static SafeFileHandle CreateFile(/*this*/ FileSystemInfo info,
            FileAccess Access, FileShare Share, IntPtr SecurityAttribute, FileMode Mode, FileAttributes Flags,
            IntPtr TemplateFile = default(IntPtr))
        {
            return Kernel32.CreateFile(info.FullName, Access, Share, SecurityAttribute, Mode, Flags, TemplateFile);
        }

        public static SafeFileHandle CreateFile(this FileSystemInfo info,
            eFileAccess Access = eFileAccess.StandardRightsWrite,
            eFileShare Share = eFileShare.Read,
            IntPtr SecurityAttribute = default(IntPtr),
            eCreationType Mode = eCreationType.OpenAlways,
            eFileAttributes Flags = eFileAttributes.Normal,
            IntPtr TemplateFile = default(IntPtr))
        {
            return Kernel32.CreateFile(info.FullName, Access, Share, SecurityAttribute, Mode, Flags, TemplateFile);
        }

        public static IEnumerable<string> GetDataStreamsNames(this FileSystemInfo info)
        {
            if(!info.Exists) yield break;

            using(var handle = CreateFile(info,
                eFileAccess.GenericRead,
                eFileShare.Read,
                IntPtr.Zero,
                eCreationType.OpenExisting,
                eFileAttributes.BackupSemantics))
            {
                if(handle.IsInvalid)
                    yield break;

                var low = 0;
                var hight = 0;
                var structure = new WIN32_STREAM_ID();

                var lBytes = Marshal.SizeOf(structure);
                var context = 0;

                do
                {
                    var lRead = 0;
                    if(!Kernel32.BackupRead(handle, ref structure, lBytes, ref lRead, false, false, ref context)
                            || lRead != lBytes)
                        yield break;

                    if(structure.dwStreamNameSize > 0)
                    {
                        lRead = 0;
                        var pBuffer = Marshal.AllocHGlobal(structure.dwStreamNameSize);

                        try
                        {
                            Kernel32.BackupRead(handle, pBuffer, structure.dwStreamNameSize, ref lRead, false, false, ref context);
                            var destination = new char[structure.dwStreamNameSize];
                            Marshal.Copy(pBuffer, destination, 0, destination.Length);
                            var name = new string(destination);
                            var index = name.IndexOf(':', 1);

                            if(index > -1)
                                name = name.Substring(1, index - 1);
                            else
                            {
                                index = name.IndexOf('\0');
                                if(index > -1)
                                    name = name.Substring(1, index - 1);
                            }

                            yield return name;
                        }
                        finally { Marshal.FreeHGlobal(pBuffer); }
                    }
                } while(Kernel32.BackupSeek(handle, structure.Size.Low, structure.Size.High, ref low, ref hight, ref context));
            }
        }

        public static IEnumerable<NTFSDataStreamInfo> GetNTFSStreamInfos(this FileSystemInfo info)
        {
            if(!info.Exists) yield break;

            using(var handle = CreateFile(info,
                eFileAccess.GenericRead,
                eFileShare.Read,
                IntPtr.Zero,
                eCreationType.OpenExisting,
                eFileAttributes.BackupSemantics))
            {
                if(handle.IsInvalid)
                    yield break;

                var low = 0;
                var hight = 0;
                var structure = new WIN32_STREAM_ID();

                var lBytes = Marshal.SizeOf(structure);
                var context = 0;

                do
                {
                    var lRead = 0;
                    if(!Kernel32.BackupRead(handle, ref structure, lBytes, ref lRead, false, false, ref context)
                            || lRead != lBytes)
                        yield break;

                    if(structure.dwStreamNameSize > 0)
                    {
                        lRead = 0;
                        var pBuffer = Marshal.AllocHGlobal(structure.dwStreamNameSize);

                        try
                        {
                            Kernel32.BackupRead(handle, pBuffer, structure.dwStreamNameSize, ref lRead, false, false, ref context);
                            var destination = new char[structure.dwStreamNameSize];
                            Marshal.Copy(pBuffer, destination, 0, destination.Length);
                            var name = new string(destination);
                            var index = name.IndexOf(':', 1);

                            if(index > -1)
                                name = name.Substring(1, index - 1);
                            else
                            {
                                index = name.IndexOf('\0');
                                if(index > -1)
                                    name = name.Substring(1, index - 1);
                            }

                            yield return new NTFSDataStreamInfo(info, name, structure);
                        }
                        finally { Marshal.FreeHGlobal(pBuffer); }
                    }
                } while(Kernel32.BackupSeek(handle, structure.Size.Low, structure.Size.High, ref low, ref hight, ref context));
            }
        }

        public static IEnumerable<string> GetStreamsNames(this FileSystemInfo info)
        {
            if(info == null) throw new ArgumentNullException(nameof(info));
            var lv_FindStreamData = new WIN32_FIND_STREAM_DATA();

            using(var handle = Kernel32.FindFirstStreamW(info.FullName, StreamInfoLevels.FindStreamInfoStandard, lv_FindStreamData))
            {
                if(handle.IsInvalid)
                    throw new Win32Exception();

                do { yield return lv_FindStreamData.cStreamName; }
                while(Kernel32.FindNextStreamW(handle, lv_FindStreamData));

                const int ERROR_HANDLE_EOF = 38;
                var lv_LastError = Marshal.GetLastWin32Error();
                if(lv_LastError != ERROR_HANDLE_EOF)
                    throw new Win32Exception(lv_LastError);
            }
        }

        public static IEnumerable<WIN32_FIND_STREAM_DATA> GetStreamsInfo(this FileSystemInfo info)
        {
            if(info == null) throw new ArgumentNullException(nameof(info));
            var lv_FindStreamData = new WIN32_FIND_STREAM_DATA();

            using(var handle = Kernel32.FindFirstStreamW(info.FullName,
                StreamInfoLevels.FindStreamInfoStandard, lv_FindStreamData))
            {
                if(handle.IsInvalid)
                    throw new Win32Exception();

                do { yield return lv_FindStreamData; }
                while(Kernel32.FindNextStreamW(handle, lv_FindStreamData));

                const int ERROR_HANDLE_EOF = 38;
                var lv_LastError = Marshal.GetLastWin32Error();
                if(lv_LastError != ERROR_HANDLE_EOF)
                    throw new Win32Exception(lv_LastError);
            }
        }
    }

    public class NTFSDataStreamInfo
    {
        private readonly FileSystemInfo f_Info;
        private readonly string f_StreamName;
        private readonly WIN32_STREAM_ID f_StreamId;

        public int Size { get { return f_StreamId.Size.Low; } }

        public long SizeLong { get { return f_StreamId.Size; } }

        public string Name { get { return f_StreamName; } }

        public string FullName { get { return string.Format("{0}:{1}:$DATA", f_Info, Name); } }

        public NTFSDataStreamInfo(FileSystemInfo Info, string StreamName, WIN32_STREAM_ID StreamId)
        {
            f_Info = Info;
            f_StreamName = StreamName;
            f_StreamId = StreamId;
        }

        public SafeFileHandle CreateFile(FileAccess Access, FileShare Share, eCreationType Mode)
        {
            eFileAccess eAccess = 0;

            if((Access & FileAccess.Read) == FileAccess.Read)
                eAccess |= eFileAccess.GenericRead;
            if((Access & FileAccess.Write) == FileAccess.Write)
                eAccess |= eFileAccess.GenericWrite;

            var file_name = FullName;
            return Kernel32.CreateFile(file_name, eAccess, (eFileShare)Share, IntPtr.Zero, Mode, eFileAttributes.None, IntPtr.Zero);
        }

        public FileStream Open(FileAccess Access, FileShare Share, eCreationType Mode)
        {
            var handle = CreateFile(Access, Share, Mode);
            return new FileStream(handle, Access);
        }

        public bool Delete() { return Kernel32.DeleteFile(FullName); }
    }
}


